package com.qijian.flowable.controller;

import cn.hutool.core.collection.CollectionUtil;
import com.qijian.common.annotation.Log;
import com.qijian.common.core.domain.AjaxResult;
import com.qijian.common.enums.BusinessType;
import com.qijian.common.utils.CommonUtil;
import com.qijian.common.utils.ObjectUtils;
import com.qijian.common.utils.SecurityUtils;
import com.qijian.common.utils.mybatis.WhereEntityTool;
import com.qijian.flowable.common.BaseFlowableController;
import com.qijian.flowable.common.FlowablePage;
import com.qijian.flowable.common.Result;
import com.qijian.flowable.constant.FlowableConstant;
import com.qijian.flowable.domain.FlowableRecord;
import com.qijian.flowable.domain.FlowableRecordPower;
import com.qijian.flowable.domain.query.FlowableRecordPowerQuery;
import com.qijian.flowable.domain.query.ProcessInstanceQueryVo;
import com.qijian.flowable.domain.vo.ProcessInstanceDetailResponse;
import com.qijian.flowable.domain.vo.ProcessInstanceRequest;
import com.qijian.flowable.domain.vo.ProcessInstanceVo;
import com.qijian.flowable.service.IFlowableRecordPowerService;
import com.qijian.flowable.service.IFlowableRecordService;
import com.qijian.flowable.service.IProcessInstanceService;
import com.qijian.flowable.wapper.CommentListWrapper;
import com.qijian.flowable.wapper.ProcInsListWrapper;
import lombok.RequiredArgsConstructor;
import org.flowable.common.engine.api.query.QueryProperty;
import org.flowable.engine.ProcessMigrationService;
import org.flowable.engine.history.HistoricProcessInstance;
import org.flowable.engine.history.HistoricProcessInstanceQuery;
import org.flowable.engine.impl.HistoricProcessInstanceQueryProperty;
import org.flowable.engine.impl.ProcessMigrationServiceImpl;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.engine.task.Comment;
import org.flowable.task.api.DelegationState;
import org.flowable.task.api.Task;
import org.flowable.task.api.TaskQuery;
import org.flowable.variable.api.history.HistoricVariableInstance;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*;

import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 庄金明
 * @date 2020年3月23日
 */
@RequiredArgsConstructor
@RestController
@RequestMapping("/flowable/processInstance")
public class ProcessInstanceController extends BaseFlowableController {

    private static final Map<String, QueryProperty> allowedSortProperties = new HashMap<>();

    private final IProcessInstanceService processInstanceService;
    private final IFlowableRecordPowerService flowableRecordPowerService;

    private final IFlowableRecordService flowableRecordService;

    private final ProcessMigrationService processMigrationService;

    static {
        allowedSortProperties.put(FlowableConstant.ID, HistoricProcessInstanceQueryProperty.PROCESS_INSTANCE_ID_);
        allowedSortProperties.put(FlowableConstant.PROCESS_DEFINITION_ID,
                HistoricProcessInstanceQueryProperty.PROCESS_DEFINITION_ID);
        allowedSortProperties.put(FlowableConstant.PROCESS_DEFINITION_KEY,
                HistoricProcessInstanceQueryProperty.PROCESS_DEFINITION_KEY);
        allowedSortProperties.put(FlowableConstant.BUSINESS_KEY, HistoricProcessInstanceQueryProperty.BUSINESS_KEY);
        allowedSortProperties.put("startTime", HistoricProcessInstanceQueryProperty.START_TIME);
        allowedSortProperties.put("endTime", HistoricProcessInstanceQueryProperty.END_TIME);
        allowedSortProperties.put("duration", HistoricProcessInstanceQueryProperty.DURATION);
        allowedSortProperties.put(FlowableConstant.TENANT_ID, HistoricProcessInstanceQueryProperty.TENANT_ID);
    }

    @PreAuthorize("@ss.hasPermi('flowable:processInstance:list')")
    @GetMapping
    public Result list(ProcessInstanceQueryVo processInstanceQueryVo) {
        HistoricProcessInstanceQuery query = historyService.createHistoricProcessInstanceQuery();

        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getProcessDefinitionCategory())) {
            query.processDefinitionCategory(processInstanceQueryVo.getProcessDefinitionCategory());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getProcessInstanceId())) {
            query.processInstanceId(processInstanceQueryVo.getProcessInstanceId());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getProcessInstanceName())) {
            query.processInstanceNameLike(processInstanceQueryVo.getProcessInstanceName());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getProcessDefinitionName())) {
            query.processDefinitionName(processInstanceQueryVo.getProcessDefinitionName());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getProcessDefinitionKey())) {
            query.processDefinitionKey(processInstanceQueryVo.getProcessDefinitionKey());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getProcessDefinitionId())) {
            query.processDefinitionId(processInstanceQueryVo.getProcessDefinitionId());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getBusinessKey())) {
            query.processInstanceBusinessKey(processInstanceQueryVo.getBusinessKey());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getInvolvedUser())) {
            query.involvedUser(processInstanceQueryVo.getInvolvedUser());
        }
        if (!processInstanceQueryVo.getFinished().equals(processInstanceQueryVo.getUnfinished())) {
            if (processInstanceQueryVo.getFinished()) {
                query.finished();
            }
            if (processInstanceQueryVo.getUnfinished()) {
                query.unfinished();
            }
        }

        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getSuperProcessInstanceId())) {
            query.superProcessInstanceId(processInstanceQueryVo.getSuperProcessInstanceId());
        }
        if (processInstanceQueryVo.getExcludeSubprocesses()) {
            query.excludeSubprocesses(processInstanceQueryVo.getExcludeSubprocesses());
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getFinishedAfter())) {
            query.finishedAfter(ObjectUtils.convertToDatetime(processInstanceQueryVo.getFinishedAfter()));
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getFinishedBefore())) {
            query.finishedBefore(ObjectUtils.convertToDatetime(processInstanceQueryVo.getFinishedBefore()));
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getStartedAfter())) {
            query.startedAfter(ObjectUtils.convertToDatetime(processInstanceQueryVo.getStartedAfter()));
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getStartedBefore())) {
            query.startedBefore(ObjectUtils.convertToDatetime(processInstanceQueryVo.getStartedBefore()));
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getStartedBy())) {
            query.startedBy(processInstanceQueryVo.getStartedBy());
        }
        // startByMe 覆盖 startedBy
        if (processInstanceQueryVo.getStartedByMe()) {
            query.startedBy(SecurityUtils.getUserId().toString());
        }
        // ccToMe 抄送我
        if (processInstanceQueryVo.getCcToMe()) {
            query.involvedUser(SecurityUtils.getUserId().toString(), FlowableConstant.CC);
        }
        if (CommonUtil.isNotEmptyAfterTrim(processInstanceQueryVo.getTenantId())) {
            query.processInstanceTenantIdLike(processInstanceQueryVo.getTenantId());
        }

        FlowablePage page = this.pageList(processInstanceQueryVo, query, ProcInsListWrapper.class, allowedSortProperties,
                HistoricProcessInstanceQueryProperty.START_TIME);
        return Result.ok(page);
    }

    @GetMapping(value = "/listMyInvolvedSummary")
    public Result listMyInvolvedSummary(ProcessInstanceQueryVo processInstanceQueryVo) {
        processInstanceQueryVo.setUserId(SecurityUtils.getUserId().toString());
        return Result.ok(this.processInstanceService.listMyInvolvedSummary(processInstanceQueryVo));
    }

    @GetMapping(value = "/listMyInvolved")
    public Result listMyInvolved(ProcessInstanceQueryVo processInstanceQueryVo) {
        processInstanceQueryVo.setInvolvedUser(SecurityUtils.getUserId().toString());
        return list(processInstanceQueryVo);
    }

    @GetMapping(value = "/listStartedByMe")
    public Result listStartedByMe(ProcessInstanceQueryVo processInstanceQueryVo) {
        processInstanceQueryVo.setStartedByMe(true);
        return list(processInstanceQueryVo);
    }

    @GetMapping(value = "/listCcToMe")
    public Result listCcToMe(ProcessInstanceQueryVo processInstanceQueryVo) {
        processInstanceQueryVo.setCcToMe(true);
        return list(processInstanceQueryVo);
    }

    @GetMapping(value = "/queryById")
    public Result queryById(@RequestParam String processInstanceId) {
        permissionService.validateReadPermissionOnProcessInstance(SecurityUtils.getUserId().toString(), processInstanceId);
        ProcessInstance processInstance = null;
        HistoricProcessInstance historicProcessInstance =
                processInstanceService.getHistoricProcessInstanceById(processInstanceId);
        if (historicProcessInstance.getEndTime() == null) {
            processInstance = processInstanceService.getProcessInstanceById(processInstanceId);
        }
        ProcessInstanceDetailResponse pidr =
                responseFactory.createProcessInstanceDetailResponse(historicProcessInstance, processInstance);
        return Result.ok(pidr);
    }

    @Log(title = "启动流程实例", businessType = BusinessType.INSERT)
    @PostMapping(value = "/start")
    @Transactional(rollbackFor = Exception.class)
    public Result start(@RequestBody ProcessInstanceVo processInstanceRequest) {
        return Result.ok(processInstanceService.start(processInstanceRequest).get("processInstanceId"));
    }

    @Log(title = "删除流程实例", businessType = BusinessType.DELETE)
    @PreAuthorize("@ss.hasPermi('flowable:processInstance:delete')")
    @DeleteMapping
    public Result delete(@RequestParam String processInstanceId, @RequestParam(required = false) boolean cascade,
                         @RequestParam(required = false) String deleteReason) {
        processInstanceService.delete(processInstanceId, cascade, deleteReason);
        return Result.ok();
    }

    @Log(title = "挂起流程实例", businessType = BusinessType.UPDATE)
    @PreAuthorize("@ss.hasPermi('flowable:processInstance:suspendOrActivate')")
    @PutMapping(value = "/suspend")
    public Result suspend(@RequestBody ProcessInstanceRequest processInstanceRequest) {
        processInstanceService.suspend(processInstanceRequest.getProcessInstanceId());
        return Result.ok();
    }

    @Log(title = "激活流程实例", businessType = BusinessType.UPDATE)
    @PreAuthorize("@ss.hasPermi('flowable:processInstance:suspendOrActivate')")
    @PutMapping(value = "/activate")
    public Result activate(@RequestBody ProcessInstanceRequest processInstanceRequest) {
        processInstanceService.activate(processInstanceRequest.getProcessInstanceId());
        return Result.ok();
    }

    @GetMapping(value = "/comments")
    public Result comments(@RequestParam String processInstanceId) {
        Boolean flag = false;
        String name = null;
        Boolean zf = false;
        permissionService.validateReadPermissionOnProcessInstance(SecurityUtils.getUserId().toString(), processInstanceId);
        //通过processInstanceId,获取sourceId,type ，在获取历史
        List<FlowableRecord> hisRecordList = flowableRecordService.getHisRecord(processInstanceId);
        List<Comment> datas = new ArrayList<>();
        if (CollectionUtil.isNotEmpty(hisRecordList)) {
            List<FlowableRecord> hisRecordListNew = new ArrayList<>();
            for (FlowableRecord flowableRecord : hisRecordList) {
                hisRecordListNew.add(flowableRecord);
                if (processInstanceId.equals(flowableRecord.getProcessInstanceId())){
                    break;
                }

            }
            for (FlowableRecord flowableRecord : hisRecordListNew) {
                List<Comment> data = taskService.getProcessInstanceComments(flowableRecord.getProcessInstanceId());
                Collections.reverse(data);
                datas.addAll(data);
            }
        }else {
            datas = taskService.getProcessInstanceComments(processInstanceId);
            Collections.reverse(datas);
        }
        datas = datas.stream().filter(d -> !d.getType().contains("RL")).collect(Collectors.toList());
        String userId = SecurityUtils.getUserId().toString();
        TaskQuery query = taskService.createTaskQuery();;
        query.taskCategory(FlowableConstant.CATEGORY_TODO);
        List<String> processInstanceIds = new ArrayList<>();
        processInstanceIds.add(processInstanceId);
        if (CollectionUtil.isNotEmpty(processInstanceIds)) {
            query.processInstanceIdIn(processInstanceIds);
        }
        List<Task> list = query.or().taskCandidateOrAssigned(userId).taskOwner(userId).endOr().list();
        if (CollectionUtil.isNotEmpty(list)) {
            flag = true;
        }
        List<Task> list1 = taskService.createTaskQuery().processInstanceId(processInstanceId).list();
        if (CollectionUtil.isNotEmpty(list1)) {
            name = list1.get(0).getName();
            for (Task task : list1) {
                if (task.getAssignee() != null && task.getAssignee().equals(SecurityUtils.getUserId().toString())) {
                    name = task.getName();
                }
            }
        }

        return Result.ok(this.listWrapper(CommentListWrapper.class, datas), flag, name, zf);
    }

    @GetMapping(value = "/formData")
    public Result formData(@RequestParam String processInstanceId) {
        Long userId = SecurityUtils.getUserId();
        HistoricProcessInstance processInstance =
                permissionService.validateReadPermissionOnProcessInstance(userId.toString(), processInstanceId);
        Object renderedStartForm = formService.getRenderedStartForm(processInstance.getProcessDefinitionId());
        Map<String, Object> variables = null;
        if (processInstance.getEndTime() == null) {
            variables = runtimeService.getVariables(processInstanceId);
        } else {
            List<HistoricVariableInstance> hisVals =
                    historyService.createHistoricVariableInstanceQuery().processInstanceId(processInstanceId).list();
            variables = new HashMap<>(16);
            for (HistoricVariableInstance variableInstance : hisVals) {
                variables.put(variableInstance.getVariableName(), variableInstance.getValue());
            }
        }
        Map<String, Object> ret = new HashMap<String, Object>(4);
        boolean showBusinessKey = isShowBusinessKey(processInstance.getProcessDefinitionId());
        ret.put("showBusinessKey", showBusinessKey);
        ret.put(FlowableConstant.BUSINESS_KEY, processInstance.getBusinessKey());
        ret.put("renderedStartForm", renderedStartForm);
        ret.put("variables", variables);

        TaskQuery query = taskService.createTaskQuery();;
        query.taskCategory(FlowableConstant.CATEGORY_TODO);
        List<String> processInstanceIds = new ArrayList<>();
        processInstanceIds.add(processInstanceId);
        if (CollectionUtil.isNotEmpty(processInstanceIds)) {
            query.processInstanceIdIn(processInstanceIds);
        }
        List<Task> list = query.or().taskCandidateOrAssigned(userId.toString()).taskOwner(userId.toString()).endOr().list();
        if (CollectionUtil.isNotEmpty(list)) {
            for (Task task : list) {
                if (permissionService.isTaskPending(task)) {
                    ret.put("zf",true);
                }
            }

        }

        // 修改成已读
        FlowableRecordPowerQuery powerQuery = new FlowableRecordPowerQuery();
        powerQuery.setProcessIntaneId(processInstanceId);
        powerQuery.setIsRead(false);
        powerQuery.setUserId(SecurityUtils.getUserId());
        FlowableRecordPower power = new FlowableRecordPower();
        power.setIsRead(true);
        flowableRecordPowerService.update(power, WhereEntityTool.invoke(powerQuery));
        return Result.ok(ret);
    }


    /**
     *
     * @return
     */
    @GetMapping(value = "/move")
    public AjaxResult move(String from,String target){
        boolean migrationValid = processMigrationService.createProcessInstanceMigrationBuilder().migrateToProcessDefinition(target).validateMigrationOfProcessInstances(from).isMigrationValid();
        if (!migrationValid) {
            return AjaxResult.error();
        }
        processMigrationService.createProcessInstanceMigrationBuilder().migrateToProcessDefinition(target).migrateProcessInstances(from);

        return AjaxResult.success();
    }
}
